package business.sale.managers;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Random;

import client.exceptions.BadClientException;
import dbExceptions.NotSaleException;
import persistence.DAO.BadEmployeeException;
import persistence.DAO.DataBaseException;
import persistence.factura.facturaDAO.FacturaDAO;
import persistence.ingredient.ingredientDAO.IngredientDAO;
import persistence.sale.saleDAO.SaleDAO;
import sale.entities.ArticleBillvo;
import sale.entities.FacturaVO;
import sale.entities.OrderVO;
import sale.entities.SaleVO;
import sale.exceptions.EmptyOrderException;
import sale.exceptions.NoSalesException;
import article.entities.ArticleVO;
import article.entities.ComboVO;
import article.entities.IngredientVO;
import article.exceptions.OutOfStockIngredientsException;
import business.article.entities.Ingredient;
import business.sale.entities.ArticleBill;
import business.sale.entities.Factura;
import business.sale.entities.Order;
import business.sale.entities.Sale;
import business.sale.interfaces.SaleMgt;

public class SaleMgr implements SaleMgt {

	/**
	 * Singleton implementation begin
	 */

	private static SaleMgr instance = null;

	private SaleMgr() {
	}

	public static SaleMgr getInstance() {
		if (instance == null)
			instance = new SaleMgr();
		return instance;
	}

	/**
	 * Singleton implementation end
	 */

	@Override
	public SaleVO[] getOpenSales() {
		SaleDAO aux = SaleDAO.getInstance();
		return this.toVOs(aux.getOpenSales());
	}

	public SaleVO newSale(OrderVO ord, int tableKey, int ci)
			throws EmptyOrderException, OutOfStockIngredientsException {
		// Si la mesa ya estaba abierta, le agrega la orden a la mesa
		// acordarse que el addOrder le paso la key de la sale, no la mesa
		Sale[] aux = SaleDAO.getInstance().getOpenSales();
		
		if (ord != null && ord.getArts() != null) {
			if(ord.getArts().size()>0){
				for (Sale i : aux) {
					if ((int) i.getSaleKey() == tableKey) {
						this.addOrder(ord, tableKey);
						return this.getSale(tableKey);
					}
				}
				Sale s;
				try {
					this.checkArticlesIngredients(ord.getArts());
					s = new Sale(ord, tableKey, ci);
					try {
						SaleDAO.getInstance().newSale(s);
					} catch (BadEmployeeException e) {
						
					} catch (BadClientException e) {
						
					}
					return s.toVO();
				} catch (OutOfStockIngredientsException e) {
					e.setOrd(ord);
					throw e;
				}
			}else
				throw new EmptyOrderException("La orden no tiene contenido");
		} else {
			throw new EmptyOrderException("La orden no tiene contenido");
		}

	}

	public SaleVO getSalePrimaryKey(int saleKey) throws NotSaleException {
		return SaleDAO.getInstance().getSalePrimaryKey(saleKey).toVO();
	}

	public SaleVO[] getOpenTakeoOutSales() throws NoSalesException {
		SaleDAO aux = SaleDAO.getInstance();
		Sale[] temp = aux.getOpenTakeoOutSales();
		if (temp != null)
			return this.toVOs(temp);
		else
			throw new NoSalesException("No open take out sales");
	}

	public SaleVO[] getOpenDeliverySales() throws NoSalesException {
		SaleDAO aux = SaleDAO.getInstance();
		Sale[] temp = aux.getOpenDeliverySales();
		if (temp != null)
			return this.toVOs(aux.getOpenDeliverySales());
		else
			throw new NoSalesException("No open delivery sales");
	}

	public SaleVO getSale(int tableKey) {
		// CHANCHADA ACA - SI TENEMOS MAS TIEMPO CAMBIAR PARA QUE TIRE EXCEPTION
		SaleDAO aux = SaleDAO.getInstance();
		try {
			return aux.getSale(tableKey).toVO();
		} catch (NotSaleException e) {
			return null;
		}
	}

	@Override
	public void addOrder(OrderVO ord, int tableKey) throws OutOfStockIngredientsException {
		SaleDAO aux = SaleDAO.getInstance();
		// OJO - LA TABLE KEY ES EN REALIDAD LA SALE KEY
		Sale temp = null;
		try {
			temp = new Sale(this.getSalePrimaryKey(tableKey));
			this.checkArticlesIngredients(ord.getArts());
			temp.addOrder(ord);
			aux.updateSale(temp);
			aux.updateTotal(temp);
		} catch (NotSaleException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (OutOfStockIngredientsException e) {
			ord.getArts().removeAll(e.getArts());
			ord.setEspecif(new ArrayList<String>());
			temp.addOrder(ord);
			aux.updateSale(temp);
			aux.updateTotal(temp);
			throw e;
		}
		
	}

	@Override
	public FacturaVO close(SaleVO sale) {
		int i;
		try {
			i = FacturaDAO.getInstance().nextFreeIdBill();
		} catch (DataBaseException e) {
			e.printStackTrace();
			Random aux = new Random();
			i = -aux.nextInt(1000);
			// Por si hay un error de BD, genero una factura con clave un numero
			// random.
		}
		Sale aux = new Sale(sale);
		ArrayList<ArticleBill> temp = aux.prepareToClose(i);
		return new FacturaVO(Calendar.getInstance().getTime(),
				this.ABtoVOs(temp), sale.getTotal(), aux.toVO());
	}

	@Override
	public void commit(FacturaVO fact) {
		// llamar al metodo de update el close para setear false
		SaleDAO.getInstance().updateClose(new Sale(fact.getSale()));
		// this.updateClose(Sale sale);
		try {
			SaleDAO.getInstance().updateClient(new Sale(fact.getSale()));
			SaleDAO.getInstance().updateEmployee(new Sale(fact.getSale()));
		} catch (BadClientException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// hay que arreglar el tema de los empleados, customer y fecha de cierre
		if (fact != null) {
			FacturaDAO.getInstance().commitFactura(new Factura(fact));
		}

	}

	@Override
	public void deleteArticles(SaleVO sale, ArrayList<ArticleVO> arts) {
		// LOCIENTO ROXXANE

	}

	public boolean hasOpenSale(int tableKey) {
		return SaleDAO.getInstance().hasOpenSale(tableKey);
	}

	public int[] getOpenTables() {
		Integer[] aux = SaleDAO.getInstance().getOpenTables();
		int[] temp = (aux != null) ? new int[aux.length] : null;
		int i = 0;
		for (Integer e : aux) {
			temp[i] = (int) e;
			i++;
		}
		return temp;
	}

	@Override
	public OrderVO[] getOpenOrders() {
		return this.toOrder(SaleDAO.getInstance().getOpenOrders());
	}


	private void checkArticlesIngredients(ArrayList<ArticleVO> arts)
			throws OutOfStockIngredientsException {
		Ingredient[] aux = IngredientDAO.getInstance().getOutStockIngredients();
		ArrayList<ArticleVO> temp = new ArrayList<ArticleVO>();
		ArrayList<IngredientVO> ingr = new ArrayList<IngredientVO>();
		for (ArticleVO o : arts) {
			if(o instanceof ComboVO){
				boolean isAdded = false;
				for(ArticleVO u : ((ComboVO) o).getArts()){
					for (IngredientVO e : u.getIngr()) {
						for (Ingredient f : aux) {
							if (e.getId() == f.getId()) {
								if(!isAdded){
									temp.add(o);
									isAdded = true;
								}									
								ingr.add(e);
							}
						}
					}
				}
			}else{
				for (IngredientVO e : o.getIngr()) {
					for (Ingredient f : aux) {
						if (e.getId() == f.getId()) {
							temp.add(o);
							ingr.add(e);
						}
					}
				}
			}			
		}
		if (temp.size() > 0) {
			ingr = this.removeDuplicates(ingr);
			temp = this.removeDuplicateArticles(temp);
			throw new OutOfStockIngredientsException(temp, ingr, 
					"Hay articulos con ingredientes fuera de stock");
		}
	}

	private ArrayList<IngredientVO> removeDuplicates(ArrayList<IngredientVO> ingr){
		ArrayList<IngredientVO> temp = new ArrayList<IngredientVO>();
		for(IngredientVO e : ingr){
			if(temp.size()==0)
				temp.add(e);
			else{
				boolean isRepeated = false;
				for(IngredientVO o : temp){
					if(o.getId()==e.getId()){
						isRepeated = true;
						break;
					}											
				}if(!isRepeated){
					temp.add(e);
				}
			}
		}return temp;
	}
	
	private ArrayList<ArticleVO> removeDuplicateArticles(ArrayList<ArticleVO> ingr){
		ArrayList<ArticleVO> temp = new ArrayList<ArticleVO>();
		for(ArticleVO e : ingr){
			if(temp.size()==0)
				temp.add(e);
			else{
				boolean isRepeated = false;
				for(ArticleVO o : temp){
					if(o.getPrimaryKey()==e.getPrimaryKey()){
						isRepeated = true;
						break;
					}											
				}if(!isRepeated){
					temp.add(e);
				}
			}
		}return temp;
	}
	
	private OrderVO[] toOrder(Order[] ord) {
		OrderVO[] temp = (ord != null) ? new OrderVO[ord.length] : null;
		int i = 0;
		for (Order o : ord) {
			temp[i] = o.toVO();
			i++;
		}
		return temp;
	}

	private SaleVO[] toVOs(Sale[] sal) {
		SaleVO[] temp = (sal != null) ? new SaleVO[sal.length] : null;
		int i = 0;
		for (Sale e : sal) {
			temp[i] = e.toVO();
			i++;
		}
		return temp;
	}

	private ArrayList<ArticleBillvo> ABtoVOs(ArrayList<ArticleBill> abo) {
		ArrayList<ArticleBillvo> temp = new ArrayList<ArticleBillvo>();
		for (ArticleBill a : abo)
			temp.add(a.toVO());
		return temp;
	}
}
